package z1err

import (
	"fmt"
	"strings"

	"gitee.com/z1gotool/z1err/pkgerrors"
)

/*
	错误处理思路总结
		本函数外处理
			直接返回错误
				defer z1err.Handle(&err) 本函数需要使用具名的error返回值，如 func1()(err error){...}

		本函数内处理
			单独处理
				在发送错误的地方处理,官方推荐
					if err != nil {...}

			集中处理，减少if err != nil {...}重复出现的次数
				在统一处理错误的地方处理
					defer z1err.Handle(nil, func(err error) {log.Println(err)}

	check/handle 方案的分析
		优点
			视觉程序较好
			实现复杂度较低
			具有通用性

		缺点
			直接返回错误时，本函数需要使用具名的error返回值
			defer在go1.14之前有额外性能开销，之后可以忽略不计
			panic/recover让函数调用的性能降低大约90%。 28.8ns/op > 271ns/op,函数调用花的时间非常少是纳秒基本所以问题不大。

*/

// https://www.cnblogs.com/zhangboyu/p/7911190.html

// Handle catching error to return or assign to recoverFunc.
// eg1:defer z1err.Handle(&err) , 直接返回错误，必须使用具名的error返回值，如func1()(err error){...}
// eg2:defer z1err.Handle(nil, func(err error) {log.Println(`err callback`,err)}) , 函数内集中处理错误
/*
	eg2.1
	defer z1err.Handle(nil, func(err error) {
		log.Println(err)
		log.Println(`-----------`)
		log.Println(z1err.StackPrint(err, 0))
		log.Println(`-----------`)
		log.Println(z1err.Is(err, `this is s test err`, `main.func1`))
		log.Println(`-----------`)
		log.Println(z1err.Is(err, `this is s test err`, `/lib1.func1`))
	})
*/
// eg3:defer z1err.Handle(nil, func(err error) {log.Println(`err callback`,err)}, func(err error) {log.Println(`nil err callback`)})
func Handle(returnErr *error, recoverFunc ...func(err error)) {
	r := recover()
	if nil != r {
		errMsg := fmt.Sprintf(`%s`, r)
		err := pkgerrors.New(errMsg)
		if returnErr != nil {
			*returnErr = err
		} else {
			if len(recoverFunc) > 0 {
				recoverFunc[0](err)
			} else {
				panic("z1error.Handle has not this logic")
			}
		}

		return
	}

	if len(recoverFunc) > 1 {
		err := fmt.Errorf("just a err placeholder")
		recoverFunc[1](err)
	}
}

// Check check and add addon msg to error or panic.
// eg1:Check(err)
// eg2:Check(err, true)
// eg3:Check(err, false)
// eg4:Check(err, true, "this is addon msg")
// eg5:Check(err, false, "this is addon msg")
func Check(e error, opt ...interface{}) (noerr bool, wrapedErr error) {
	if e != nil {
		noerr = false

		if 0 == len(opt) {
			//只附加新的信息
			// func WithMessage(err error, message string) error
			//只附加调用堆栈信息
			wrapedErr = pkgerrors.WithStack(e)
			panic(wrapedErr)
		} else {
			msg := ``
			if len(opt) > 1 {
				myMsg, ok := opt[1].(string)
				if ok {
					msg = myMsg
				}
			}

			//同时附加堆栈和信息
			wrapedErr = pkgerrors.Wrap(e, msg)

			if len(opt) > 0 {
				isPanic, ok := opt[0].(bool)
				if ok && isPanic {
					panic(wrapedErr)
				}
			}
		}
	} else {
		noerr = true
	}

	return
}

// StackPrint print msg with stack and can set skip.
// eg:z1err.StackPrint(err)
// eg:z1err.StackPrint(err, 7)
func StackPrint(err error, skip ...int) (str string) {
	// https://studygolang.com/articles/17430?fr=sidebar
	skipFlag := 7
	if len(skip) > 0 {
		skipFlag = skip[0]
	}
	errCause := fmt.Sprintf(`%v`, pkgerrors.Cause(err))
	errInfo := fmt.Sprintf(`%+v`, err)
	errInfo2 := strings.Replace(errInfo, errCause, "", -1)
	errInfoArr := strings.Split(errInfo2, "\n")
	if len(errInfoArr) > skipFlag {
		str = errCause + "\n" + strings.Join(errInfoArr[skipFlag:], "\n")
	} else {
		str = errCause
	}

	return
}

// Is Check error value.
// eg:z1err.Is(err, `this is s test err`)
// eg:z1err.Is(err, `this is s test err`, `main.func1`)
// eg:z1err.Is(err, `this is s test err`, `/rs.func1`)
func Is(err error, msgFlag string, funcFlag ...string) (ok bool) {
	str := fmt.Sprintf(`%v`, err)
	if !strings.Contains(str, msgFlag) {
		return false
	}

	if len(funcFlag) > 0 {
		str = StackPrint(err, 5)
		if strings.Contains(str, `/z1err.Check`) {
			str = StackPrint(err, 7)
		}

		if !strings.Contains(str, funcFlag[0]) {
			return false
		}
	}

	return true
}
